fix(cli): exit via process::exit to avoid tokio stdin-blocking-pool hang - #321
Merged
Conversation
bug-ops
force-pushed
the
308-sigterm-stdin-hang
branch
from
August 5, 2026 01:38
d18cbbd to
df8d77d
Compare
bug-ops
enabled auto-merge (squash)
August 5, 2026 01:39
mcpls previously did not exit on SIGTERM/SIGINT while a stdio MCP client kept its stdin write end open. rmcp::transport::stdio() is backed by tokio::io::stdin(), which internally parks an uncancellable spawn_blocking thread in a raw read() syscall; #[tokio::main]'s generated wrapper blocks in Runtime::drop waiting for that thread once main's body returns, even though all real shutdown work (LSP server teardown, log flush) had already completed by then. main now calls std::process::exit as its final step instead of returning normally, terminating immediately once run().await resolves and bypassing the blocking-pool wait. Scoped to crates/mcpls-cli/src/main.rs only; mcpls-core's serve/serve_with/ shutdown/run_stdio are unaffected and keep normal Result-returning semantics for library embedders. Also fixes await_lsp_init_handle's timeout branch, which called JoinHandle::abort() without a subsequent await: on a SIGTERM arriving mid-spawn_batch (before any server registers), process::exit could run before the runtime dropped the aborted task's locals, including not-yet-registered LSP Child handles relying on kill_on_drop, orphaning those processes. abort() is now followed by a bounded re-await so that drop happens before main exits. Fixes #308
bug-ops
force-pushed
the
308-sigterm-stdin-hang
branch
from
August 5, 2026 01:43
df8d77d to
6a051b4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SIGTERM/SIGINTwhile a stdio MCP client kept its stdin write end open.rmcp::transport::stdio()is backed bytokio::io::stdin(), which internally parks an uncancellablespawn_blockingthread in a rawread()syscall;#[tokio::main]'s generated wrapper blocks inRuntime::dropwaiting for that thread oncemain's body returns, even though all real shutdown work (LSP server teardown, log flush) had already completed.mainnow callsstd::process::exitas its final step instead of returning normally. Scoped tocrates/mcpls-cli/src/main.rsonly;mcpls-core'sserve/serve_with/shutdown/run_stdioare unaffected.await_lsp_init_handle's timeout branch, which calledJoinHandle::abort()without a subsequent await: aSIGTERMarriving mid-spawn_batchcould letprocess::exitrun before the runtime dropped the aborted task's locals (including not-yet-registered LSPChildhandles relying onkill_on_drop), orphaning those processes — the exact failure mode fix(transport): handle SIGINT/SIGTERM in stdio transport and gracefully shut down LSP servers #270 guards against.abort()is now followed by a bounded re-await.lib.rs,transport.rs,crates/mcpls-core/README.md) that previously taught the pre-fix hanging pattern.test_e2e_sigterm_exits_promptly_while_client_stdin_open,#[cfg(unix)]) that spawns the real binary, completes an MCP handshake, sendsSIGTERM, and asserts prompt exit with status 0.Fixes #308. Filed #318 as a separate, non-blocking follow-up for a distinct pre-existing race surfaced during test hardening (SIGTERM arriving before
run_stdio's signal handler registers gets OS default disposition).Test plan
cargo +nightly fmt --all -- --checkcargo clippy --all-targets --all-features --workspace -- -D warningscargo nextest run --workspace --all-features --lib --bins(639/639)RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features--run-ignored ignored-only), 8/8 consecutive passes